package net.zoneland.cas;

import static net.zoneland.cas.CasAppHelper.getProperty;

import java.security.PublicKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.AbstractUrlBasedTicketValidator;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.jasig.cas.client.validation.TicketValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.zoneland.cas.util.ClientFutureConst;

public class CustomAssertionUtils {
    /** Logger for this class */
    private static final Logger LOG = LoggerFactory.getLogger(CustomAssertionUtils.class);
    public static final String KEY_SSO_OK = "_sso_ok";
    private static final String CONST_CAS_ASSERTION = AbstractCasFilter.CONST_CAS_ASSERTION;

    private static final class Const {
        static final String KEY_TGC = CasAppHelper.getProperty("tgc.name", "CASTGC");
        //最后登录用户UID cookie cas_last_login_uid_cookie=last_login_name
        static final String KEY_LAST_LOGIN_UID_COOKIE = getProperty("cas_last_login_uid_cookie",null); //last_login_name

        static final boolean CREATE_SESSION_IF = CasAppHelper.getBoolean("cas.create_session_if", true);
        private static final PublicKey PUBLIC_KEY;

        static {
            String p = CasAppHelper.getProperty("cas.client_publicKey", null);
            PUBLIC_KEY = p == null || p.length() < 1 ? null : CasAppHelper.toPublicKey(p);
        }
    }

    private CustomAssertionUtils() {
        super();
    }

    public static Assertion validate(final AbstractUrlBasedTicketValidator validator, final String ticket,
        final String service) throws TicketValidationException {
        // final String serverResponse =
        // "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n"
        // +
        // "<cas:authenticationSuccess><cas:user>abc</cas:user></cas:authenticationSuccess>\n"
        // + "</cas:serviceResponse>";
        // return validator.parseResponseFromServer(serverResponse);
        // return new AssertionImpl(new AttributePrincipalImpl("abcd", new HashMap()));
        return validate(ticket);
    }

    public static Assertion validate(final String ticket, final HttpServletRequest request) {
        final Assertion ret = (Assertion)request.getAttribute(CONST_CAS_ASSERTION);
        return ret != null ? ret : validate(ticket);
    }

    protected static Assertion validate(final String ticket) {
        if (ClientFutureConst.UID_ON_TICKET && Const.PUBLIC_KEY != null && ticket.length() > 8
            && ticket.charAt(7) == '-' && ticket.startsWith("ST-k")) {
            // String key = ticket.substring(4, 8);
            final PublicKey publicKey = Const.PUBLIC_KEY; // CasCustomHelper.getPublicKey(key);
            if (publicKey != null) {
                try {
                    final String ticket1 = ticket.substring(8);
                    final String algorithm = ClientFutureConst.ALGORITHM_SWITCHABLE ? getAlgorithm(ticket.charAt(7))
                        : ClientFutureConst.ALGORITHM;
                    final String src = CasAppHelper.decrptyBase64UrlSafe(ticket1, publicKey, algorithm);
                    final String[] parts = src.split("#");
                    final long time = toETime(parts);
                    if (time <= ClientFutureConst.TICKET_VALID_MAX_SECONDS) {
                        return toAssertion(parts[0]);
                    } else {
                        LOG.warn("ticket timeout:{} disable decrpty!time={}",
                            ClientFutureConst.TICKET_VALID_MAX_SECONDS, time);
                    }
                } catch (IllegalStateException ex) {
                    if (ClientFutureConst.IGNORE_CLIENT_DECRYPT_ERROR) {
                        if (LOG.isDebugEnabled()) {
                            LOG.warn("ignore:{}", ex, ex);
                        } else {
                            LOG.warn("ignore:{}", ex.getMessage());
                        }
                    } else {
                        throw ex;
                    }
                }
            }
        }
        return null;
    }

    private static String getAlgorithm(final char ch) {
        String algorithm = ClientFutureConst.ALGORITHM;
        if (ch == 'a' && (algorithm == null || !ClientFutureConst.ALG_AES)) {
            algorithm = "AES";
        } else if (algorithm != null && ch != 'a' && ClientFutureConst.ALG_AES) {
            algorithm = null;
        }
        return algorithm;
    }

    private static long toETime(String[] parts) {
        final long time = System.currentTimeMillis() - Long.valueOf(parts[1], 36);
        return time;
    }

    /**
     * 判断当前请求所在应用已登录用户信息是否和last_login_name相同,不同的话则要重新sso <br/>
     * 前提全局配置中要设置: `cas_last_login_uid_cookie=last_login_name`
     */
    private static boolean isNotLastLoginUser(final HttpServletRequest request, final Assertion assertion) {
        if (Const.KEY_LAST_LOGIN_UID_COOKIE == null) {
            return false;
        }
        final Cookie[] cookies = request.getCookies();
        String lastUid = null;
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(Const.KEY_LAST_LOGIN_UID_COOKIE)) {
                lastUid = cookie.getValue();
                break;
            }
        }
        return (lastUid != null) && (!lastUid.equals(assertion.getPrincipal().getName()));
    }
   
    public static Assertion getAssertionIf(final HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        Assertion assertion = session != null ? (Assertion)session.getAttribute(CONST_CAS_ASSERTION) : null;
        if (assertion != null && isNotLastLoginUser(request, assertion)) {
            assertion = null;
        }
       
        if (assertion == null && ClientFutureConst.UID_ON_COOKIE && Const.PUBLIC_KEY != null) {
            final Cookie[] cookies = request.getCookies();
            String casTgc = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (Const.KEY_TGC.equals(cookie.getName())) {
                        casTgc = cookie.getValue();
                        break;
                    }
                }
            }
            if (casTgc != null && casTgc.length() > 8 && casTgc.charAt(8) == '-' && casTgc.startsWith("TGT-k")) {
                // String key = casTgc.substring(5, 9);
                final PublicKey publicKey = Const.PUBLIC_KEY; // CasCustomHelper.getPublicKey(key);
                if (publicKey != null) {
                    try {
                        final String algorithm = ClientFutureConst.ALGORITHM_SWITCHABLE ? getAlgorithm(casTgc.charAt(8))
                            : ClientFutureConst.ALGORITHM;
                        final String ticket1 = casTgc.substring(9);
                        final String src = CasAppHelper.decrptyBase64UrlSafe(ticket1, publicKey, algorithm);
                        final String[] parts = src.split("#");
                        assertion = toAssertion(parts[0]);

                        final long time = toETime(parts);
                        if (time <= ClientFutureConst.COOKIE_VALID_MAX_SECONDS) {
                            assertion = toAssertion(parts[0]);
                        } else {
                            LOG.warn("ticket timeout:{} disable decrpty!time={}",
                                ClientFutureConst.COOKIE_VALID_MAX_SECONDS, time);
                        }

                    } catch (IllegalStateException ex) {
                        if (ClientFutureConst.IGNORE_CLIENT_DECRYPT_ERROR) {
                            if (LOG.isDebugEnabled()) {
                                LOG.warn("ignore:{}", ex, ex);
                            } else {
                                LOG.warn("ignore:{}", ex.getMessage());
                            }
                        } else {
                            throw ex;
                        }
                    }
                    if (assertion != null) {
                        if (session == null && Const.CREATE_SESSION_IF) {
                            session = request.getSession(true);
                        }
                        if (session != null) {
                            request.setAttribute(CONST_CAS_ASSERTION, assertion);
                            session.setAttribute(CONST_CAS_ASSERTION, assertion);
                        }
                    }
                }
            }
        }
        return assertion;
    }

    private static Assertion toAssertion(final String uid) {
        return new AssertionImpl(new AttributePrincipalImpl(uid));
    }

    // public static final String constructServiceUrl(final HttpServletRequest request) {
    // //String string = request.getRequestURL().toString()
    //// String contextPath = request.getContextPath();
    //// String url = request.getRequestURL().toString();
    //
    // return null;
    // }

    public static final String constructLoginUrl(final String servicUrl) {
        if (LoginUrlMapping.LOGIN_URL_MAPPING == null) {
            return null;
        }
        for (Map.Entry<Pattern, String> entry : LoginUrlMapping.LOGIN_URL_MAPPING.entrySet()) {
            if (entry.getKey().matcher(servicUrl).find()) {
                return entry.getValue();
            }
        }
        return null;
    }

    public static final String constructLogoutUrl(final String servicUrl) {
        if (LoginUrlMapping.LOGOUT_URL_MAPPING == null) {
            return null;
        }
        for (Map.Entry<Pattern, String> entry : LoginUrlMapping.LOGOUT_URL_MAPPING.entrySet()) {
            if (entry.getKey().matcher(servicUrl).find()) {
                return entry.getValue();
            }
        }
        return null;
    }

    private static ServiceUrlGetter serviceUrlGetter;

    public static ServiceUrlGetter getServiceUrlGetter() {
        return serviceUrlGetter;
    }

    public static void setServiceUrlGetter(ServiceUrlGetter serviceUrlGetter) {
        CustomAssertionUtils.serviceUrlGetter = serviceUrlGetter;
    }

    public static String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
        return serviceUrlGetter == null ? null : serviceUrlGetter.constructServiceUrl(request, response);
    }

    private static final class LoginUrlMapping {
        static final Map<Pattern, String> LOGIN_URL_MAPPING;
        static final Map<Pattern, String> LOGOUT_URL_MAPPING;

        static {
            String input = CasAppHelper.getProperty("cas.client_server_url_mapping", null);
            if (input == null || input.length() < 1) {
                LOGIN_URL_MAPPING = null;
                LOGOUT_URL_MAPPING = null;
            } else {
                Map<String, String> mapping = CasAppHelper.json2object(input, Map.class);
                Map<Pattern, String> loginUrlMapping = new HashMap<Pattern, String>();
                Map<Pattern, String> logoutUrlMapping = new HashMap<Pattern, String>();
                for (Map.Entry<String, String> entry : mapping.entrySet()) {
                    String url = entry.getValue();
                    loginUrlMapping.put(Pattern.compile(entry.getKey()), url + "/login");
                    logoutUrlMapping.put(Pattern.compile(entry.getKey()), url + "/logout");
                }
                LOGIN_URL_MAPPING = Collections.unmodifiableMap(loginUrlMapping);
                LOGOUT_URL_MAPPING = Collections.unmodifiableMap(logoutUrlMapping);
            }
        }
    }
}
